﻿using System;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using PropertyExpression.ControllableQuery.Toolkit.Test.Queries;

namespace PropertyExpression.ControllableQuery.Toolkit.Test
{
    [TestFixture]
    public class MainTest
    {
        //TODO: uncoment and delete this
//        [Test]
//        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"SqlException was thrown.
//Message = 'Invalid column name 'LanguageCode2'.'
//QueryType = 'PropertyExpression.ControllableQuery.Toolkit.Test.Queries.Error1Query'
//LineNumber = '32'
//SqlCommandTextWithLineNumbers = 
//  1:SELECT
//  2:    BillOfMaterialsLine.PartNumber
//  3:FROM
//  4:    BillOfMaterialsLine
//  5:WHERE
//  6:    BillOfMaterialsHeaderId = @p0
//  7:    AND (
//  8:         (
//  9:          BillOfMaterialsLine.MainLinkExcludedFrom = 0
// 10:          AND EXISTS ( SELECT
// 11:                        1
// 12:                       FROM
// 13:                        MainPartLink
// 14:                       WHERE
// 15:                        MainPartLink.PartNumber = BillOfMaterialsLine.PartNumber
// 16:                        AND MainPartLink.ModelPublicationId = @p1
// 17:                        AND MainPartLink.ServicePartsPageId <> @p2 )
// 18:         )
// 19:         OR (
// 20:             BillOfMaterialsLine.InnerLinkExcludedFrom = 0
// 21:             AND EXISTS ( SELECT
// 22:                            1
// 23:                          FROM
// 24:                            InnerPartLink
// 25:                            INNER JOIN Model
// 26:                                ON InnerPartLink.ModelCode = Model.ModelCode
// 27:                                   AND InnerPartLink.ModelSequence = Model.ModelSequence
// 28:                            INNER JOIN Publication
// 29:                                ON InnerPartLink.PublicationId = Publication.PublicationId
// 30:                            INNER JOIN LocalizedPublication
// 31:                                ON InnerPartLink.PublicationId = LocalizedPublication.PublicationId
//*32:                                   AND LocalizedPublication.LanguageCode2  = @p3
// 33:                          WHERE
// 34:                            InnerPartLink.PartNumber = BillOfMaterialsLine.PartNumber
// 35:                            AND Model.ModelId = @p4
// 36:                            AND Publication.FollowingPublicationId IS NULL
// 37:						)
// 38:            )
// 39:        )
//SqlCommandText = 
//SELECT
//    BillOfMaterialsLine.PartNumber
//FROM
//    BillOfMaterialsLine
//WHERE
//    BillOfMaterialsHeaderId = @p0
//    AND (
//         (
//          BillOfMaterialsLine.MainLinkExcludedFrom = 0
//          AND EXISTS ( SELECT
//                        1
//                       FROM
//                        MainPartLink
//                       WHERE
//                        MainPartLink.PartNumber = BillOfMaterialsLine.PartNumber
//                        AND MainPartLink.ModelPublicationId = @p1
//                        AND MainPartLink.ServicePartsPageId <> @p2 )
//         )
//         OR (
//             BillOfMaterialsLine.InnerLinkExcludedFrom = 0
//             AND EXISTS ( SELECT
//                            1
//                          FROM
//                            InnerPartLink
//                            INNER JOIN Model
//                                ON InnerPartLink.ModelCode = Model.ModelCode
//                                   AND InnerPartLink.ModelSequence = Model.ModelSequence
//                            INNER JOIN Publication
//                                ON InnerPartLink.PublicationId = Publication.PublicationId
//                            INNER JOIN LocalizedPublication
//                                ON InnerPartLink.PublicationId = LocalizedPublication.PublicationId
//                                   AND LocalizedPublication.LanguageCode2  = @p3
//                          WHERE
//                            InnerPartLink.PartNumber = BillOfMaterialsLine.PartNumber
//                            AND Model.ModelId = @p4
//                            AND Publication.FollowingPublicationId IS NULL
//						)
//            )
//        )
//Class = '16'
//Number = '207'
//State = '1'
//ErrorCode = '-2146232060'
//Procedure = ''
//Server = '(local)'
//Source = '.Net SqlClient Data Provider'")]
//        public void Error1Query()
//        {
//            CheckQueries(
//                ConnectionString,
//                queryContext => new Error1Query(
//                                    queryContext.CreateGuidParam(),
//                                    queryContext.CreateGuidParam(),
//                                    queryContext.CreateGuidParam(),
//                                    new WithInnerPartLinkSwitcher(
//                                        true,
//                                        queryContext.CreateStringParam(),
//                                        queryContext.CreateGuidParam())
//                                    )
//                );
//        }

        [Test]
        public void Error2Query()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new Error2Query(
                                    queryContext.CreateStringParam()
                                    )
                );
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(SqlException), ExpectedMessage = "The variable name '@p1' has already been declared. Variable names must be unique within a query batch or stored procedure.")]
        public void Error3FixQuery()
        {
            ControllableQuery.Test.MainTest.ExecuteReader(
                queryContext => new Error3Query(
                                    queryContext.Param("ModelCode1"),
                                    queryContext.Param("ModelSequence1")
                                    )
                ).ToList();
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = "The variable name '@p1' has already been declared. Variable names must be unique within a query batch or stored procedure.", MatchType = MessageMatch.Contains)]
        public void Error3FixQueryQueryChecker()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new Error3Query(
                                    queryContext.CreateStringParam(),
                                    queryContext.CreateStringParam()
                                    )
                );
        }

        //TODO: uncoment and delete this
        //[Test]
        //[ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = "Cannot find data type IPublicationCodeType2.", MatchType = MessageMatch.Contains)]
        //public void Error4Query()
        //{
        //    CheckQueries(
        //        ConnectionString,
        //        queryContext => new Error4Query(
        //                            queryContext.CreateTableValuedParam<IPublicationCodeType2>()
        //                            )
        //        );
        //}

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = "The type of query result property was incorrect ColumnName = 'PublicationTypeCode'. Expected SQL Server Database Engine type DataType = 'int', AllowDBNull = 'True' but was .NET Framework type 'System.Int32'.", MatchType = MessageMatch.Contains)]
        public void Error8Query()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new Error8Query()
                );
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = "The type of query result property was incorrect ColumnName = 'PublicationTypeCode'. Expected SQL Server Database Engine type DataType = 'int', AllowDBNull = 'True' but was .NET Framework type 'System.String'.", MatchType = MessageMatch.Contains)]
        public void Error9Query()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new Error9Query()
                );
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = "The query result type does not contain a definition for property 'PublicationTypeCode'.", MatchType = MessageMatch.Contains)]
        public void Error10Query()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new Error10Query()
                );
        }

        private static void CheckQueries<T>(string connectionString, params Func<SqlCommand, IQuery<T>>[] queryFuncs)
        {
            QueryChecker.CheckQueries(connectionString, ControllableQuery.Test.QueryCheck.MainTest.QueryResultPropertyTypeChecker, queryFuncs);
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = "The query result does not contain a column 'PublicationTypeCode2'.", MatchType = MessageMatch.Contains)]
        public void Error11Query()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new Error11Query()
                );
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"'Must declare the scalar variable ""@p0AND", MatchType = MessageMatch.Contains)]
        public void Error12Query()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new Error12Query(
                                    queryContext.CreateStringParam(),
                                    queryContext.CreateStringParam()
                                    )
                );
        }

        private static void CheckAllQueries(Type type)
        {
            try
            {
                QueryChecker.CheckAllQueries(
                    ConnectionString,
                    ControllableQuery.Test.QueryCheck.MainTest.QueryResultPropertyTypeChecker,
                    ControllableQuery.Test.QueryCheck.MainTest.ParamCreator,
                    ControllableQuery.Test.QueryCheck.MainTest.QueryTypePredicate,
                    ControllableQuery.Test.QueryCheck.MainTest.MethodInfoPredicate,
                    ControllableQuery.Test.QueryCheck.MainTest.ChoiceInvocationGetter,
                    new[] {type},
                    new DoNothingQRepository());
            }
            catch (TargetInvocationException e)
            {
                throw e.InnerException;
            }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"Invalid column name 'PublicationTypeId2'", MatchType = MessageMatch.Contains)]
        public void Error13Query()
        {
            CheckAllQueries(typeof(Query1));
        }

        private static class Query1
        {
            public static INonQuery GetQuery()
            {
                return new Q("INSERT PublicationType (PublicationTypeId2) VALUES('')").NonQuery();
            }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"Invalid column name 'PublicationTypeId2'", MatchType = MessageMatch.Contains)]
        public void Error14Query()
        {
            CheckAllQueries(typeof(Query2));
        }

        private static class Query2
        {
            public static IQuery<IInfo> GetQuery()
            {
                return new Q("SELECT PublicationTypeId2 FROM PublicationType").Query<IInfo>();
            }

            public interface IInfo { }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"Method is not static", MatchType = MessageMatch.Contains)]
        public void Error15Query()
        {
            CheckAllQueries(typeof(Query3));
        }

        private class Query3
        {
            public INonQuery GetQuery()
            {
                return new Q("INSERT PublicationType (PublicationTypeId2) VALUES('')").NonQuery();
            }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"Method is not static", MatchType = MessageMatch.Contains)]
        public void Error16Query()
        {
            CheckAllQueries(typeof(Query4));
        }

        private class Query4
        {
            public IQuery<IInfo> GetQuery()
            {
                return new Q("SELECT PublicationTypeId2 FROM PublicationType").Query<IInfo>();
            }

            public interface IInfo { }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"method is called in a constructor of type", MatchType = MessageMatch.Contains)]
        public void Error17Query()
        {
            CheckAllQueries(typeof(Query5));
        }

        private class Query5
        {
            public Query5()
            {
                new Q("INSERT PublicationType (PublicationTypeId2) VALUES('')").NonQuery();
            }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"method is called in a constructor of type", MatchType = MessageMatch.Contains)]
        public void Error18Query()
        {
            CheckAllQueries(typeof(Query6));
        }

        private class Query6
        {
            public Query6()
            {
                new Q("SELECT PublicationTypeId2 FROM PublicationType").Query<IInfo>();
            }

            private interface IInfo { }
        }

        private static string ConnectionString
        {
            get { return ControllableQuery.Test.MainTest.ConnectionString; }
        }

        static void QTest<TParams, TRecordset>(
            Func<SqlCommand, TParams> paramsFunc, Func<TParams, IQuery<TRecordset>> recordsetFunc)
        {

        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"The instance of IQuery is ambiguous", MatchType = MessageMatch.Contains)]
        public void Error19Query()
        {
            CheckAllQueries(typeof(Query7));
        }

        private class Query7
        {
            void M1()
            {
                QTest(
                    context => new { },
                    p => {
                        new Q("SELECT PublicationTypeId FROM PublicationType")
                            .Query(new { PublicationTypeId = default(Guid) });
                        return new Q("SELECT PublicationTypeId FROM PublicationType")
                            .Query(new {PublicationTypeId = default(Guid)});
                    });                
            }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"The instance of IQuery is ambiguous", MatchType = MessageMatch.Contains)]
        public void Error20Query()
        {
            CheckAllQueries(typeof(Query8));
        }

        private class Query8
        {
            private void M1()
            {
                QTest(
                    context => new {},
                    p => {
                        new Query2();
                        return new Q("SELECT PublicationTypeId FROM PublicationType")
                            .Query(new {PublicationTypeId = default(Guid)});
                    });
            }

            private class Query2: IQuery
            {
                public string TransformText()
                {
                    throw new NotImplementedException();
                }
            }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"The instance of IQuery is ambiguous", MatchType = MessageMatch.Contains)]
        public void Error21Query()
        {
            CheckAllQueries(typeof(Query9));
        }

        public class Query9
        {
            private void M1()
            {
            }

            private interface IAnonymousClass
            {
                Guid PublicationTypeId { get; set; }
            }

#pragma warning disable 649
            private static IQuery<IAnonymousClass> query;
#pragma warning restore 649
            private static IQuery<IAnonymousClass> Query()
            {
                new Q("SELECT PublicationTypeId FROM PublicationType")
                    .Query<IAnonymousClass>();
                return query;
            }
        }

        [Test]
        [ExpectedException(ExpectedException = typeof(QueryCheckException), ExpectedMessage = @"The instance of IQuery is ambiguous", MatchType = MessageMatch.Contains)]
        public void Error22Query()
        {
            CheckAllQueries(typeof(Query10));
        }

        public class Query10
        {
            private void M1()
            {
                QTest(
                    context => new {},
                    p => {
                        var m1 = new SomeClass().M1;
                        return new Q("SELECT PublicationTypeId FROM PublicationType")
                            .Query(new {PublicationTypeId = default(Guid)});
                    });
            }

            private class SomeClass
            {
                public IQuery M1
                {
                    get
                    {
                        return new Q("SELECT PublicationTypeId FROM PublicationType")
                            .Query(new {PublicationTypeId = default(Guid)});
                    }
                }
            }
        }
    }
}